package com.zzs.judge.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONObject;
import com.github.dockerjava.api.DockerClient;
import com.zzs.common.core.entity.msg.CodeSubmitMessage;
import com.zzs.common.core.entity.redis.ProblemCodeCache;
import com.zzs.common.core.entity.redis.TestCaseCache;
import com.zzs.common.core.enums.ResponseCode;
import com.zzs.common.security.exception.ServiceException;
import com.zzs.judge.callback.ExecResultCallback;
import com.zzs.judge.callback.MemoryStatsCallback;
import com.zzs.judge.constants.DockerConsts;
import com.zzs.judge.dao.redis.ProblemCodeCacheManager;
import com.zzs.judge.dao.redis.TestCaseCacheManager;
import com.zzs.judge.pojo.other.CodeRunResult;
import com.zzs.judge.pojo.other.CompileResult;
import com.zzs.judge.pojo.other.RunResult;
import com.zzs.judge.service.ExeJavaCodeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.concurrent.TimeUnit;


/**
 * @author zzs
 * @date 2025/03/29
 */
@Service
@Slf4j
public class ExeJavaCodeServiceImpl implements ExeJavaCodeService {
    @Autowired
    private ProblemCodeCacheManager problemCodeCacheManager;

    @Autowired
    private TestCaseCacheManager testCaseCacheManager;

    @Autowired
    private DockerClient dockerClient;

    @Value("${docker.java.time}")
    private long maxWaitTime;

    @Value("${docker.java.memory}")
    private long maxUseMemory;

    /**
     * 将用户提交的代码, 在本地生成一个Solution-userId.java文件, 其中userId替换为真实的用户id.
     * 这个本地的java文件会自动挂载到容器
     */
    @Override
    public void createJavaCodeFile(CodeSubmitMessage codeSubmitMessage) {
//        创建用户代码文件
        ProblemCodeCache problemCodeCache = problemCodeCacheManager.getProblemCodeCache(codeSubmitMessage.getProblemId(), codeSubmitMessage.getLanguage());
        String finalCode = mergeJavaCode(problemCodeCache.getMainFunction(), codeSubmitMessage.getCode());
        FileUtil.writeUtf8String(finalCode, getUserLocalJavaCodeDir(codeSubmitMessage.getUserId()) + DockerConsts.WINDOW_SEPARATOR + DockerConsts.JAVA_CLASS_NAME);
//        在指定下载jar文件(如果没有的话)
        downloadJars();
//        创建用户算法对应的测试文件
        TestCaseCache testCaseCache = testCaseCacheManager.get(codeSubmitMessage.getProblemId());
        FileUtil.writeUtf8String(testCaseCache.getTestCase(), getUserLocalJavaCodeDir(codeSubmitMessage.getUserId()) + DockerConsts.WINDOW_SEPARATOR + DockerConsts.TEST_CASE_FILE);
    }

    private void downloadJars() {
        String localLibsDir = DockerConsts.LOCAL_JAVA_LIBS_DIR + DockerConsts.WINDOW_SEPARATOR;
        if (!FileUtil.exist(localLibsDir + DockerConsts.FASTJSON2_JAR)) {
            HttpUtil.downloadFile(DockerConsts.FASTJSON2_JAR_DOWNLOAD_URL, localLibsDir + DockerConsts.FASTJSON2_JAR);
        }
        if (!FileUtil.exist(localLibsDir + DockerConsts.LOMBOK_JAR)) {
            HttpUtil.downloadFile(DockerConsts.LOMBOK_JAR_DOWNLOAD_URL, localLibsDir + DockerConsts.LOMBOK_JAR);
        }
    }

    @Override
    public CompileResult compileJavaCode(String containerId, long userId) {
        String compileCmdId = getCompileCmdId(containerId, userId);
        try (ExecResultCallback callback = new ExecResultCallback()) {
            dockerClient.execStartCmd(compileCmdId)
                    .exec(callback)
                    .awaitCompletion(maxWaitTime, TimeUnit.SECONDS);
            if (callback.isSuccess()) {
                return CompileResult.pass();
            } else {
                return CompileResult.errorCompile(callback.getStderr());
            }
        } catch (IOException e ) {
            log.error(e.getMessage());
            return CompileResult.errorServer();
        } catch (InterruptedException e) {
            log.error(e.getMessage());
            return CompileResult.errorCompile("编译超时");
        }
    }

    @Override
    public RunResult runJavaCode(String containerId, long userId) {
//            内存监控
        MemoryStatsCallback memoryStatsCallback = dockerClient.statsCmd(containerId).exec(new MemoryStatsCallback());
        String runCmdId = getRunCmdId(containerId, userId);
        try (ExecResultCallback callback = new ExecResultCallback()) {
            dockerClient.execStartCmd(runCmdId).exec(callback).awaitCompletion(maxWaitTime, TimeUnit.SECONDS);
            memoryStatsCallback.close();
            if (!callback.isSuccess()) {
//              出现异常情况(注意, 不是用户代码逻辑引起的异常, 而是由于docker连接出现异常之类的)
                log.error(callback.getStderr());
                return RunResult.errorServer();
            }
//            判断使用内存是否超出限制
            long useMemory = memoryStatsCallback.getMaxUseMemory();
            if (useMemory > maxUseMemory) {
                return RunResult.exceedMemory();
            }
//            成功执行用户代码, 获取用户代码输出
            CodeRunResult codeRunResult = JSONObject.parseObject(callback.getStdout(), CodeRunResult.class);
            RunResult runResult = new RunResult();
            BeanUtils.copyProperties(codeRunResult, runResult);
            runResult.setUseMemory(useMemory);
            return runResult;
        } catch (IOException e) {
            log.error(e.getMessage());
            return RunResult.errorServer();
        } catch (InterruptedException e) {
            log.error(e.getMessage());
            return RunResult.exceedTime();
        }
    }

    @Override
    public void removeJavaCodeFile(long userId) {
        FileUtil.del(getUserLocalJavaCodeDir(userId));
    }

    public static String getUserLocalJavaCodeDir(long userId) {
        return DockerConsts.LOCAL_JAVA_CODE_DIR + DockerConsts.WINDOW_SEPARATOR + userId;
    }


    /**
     * 将main函数和用户提交的代码合并起来, 组成完整的类结构
     *
     * @param mainFunction 用户不可见, 后台执行的main方法
     * @param userCode     用户提交的代码
     * @return 完整的Solution类
     */
    private String mergeJavaCode(String mainFunction, String userCode) {
        // 查找用户代码中最后一个右大括号的位置，用于插入main方法
        int lastIndex = userCode.lastIndexOf("}");
        // 如果未找到右大括号，抛出代码格式错误异常
        if (lastIndex < 0) {
            throw new ServiceException(ResponseCode.FAIL, "代码格式错误");
        }
        // 在用户代码的最后一个右大括号之前插入main方法，形成完整的类代码
        return userCode.substring(0, lastIndex) + "\n" + mainFunction + "\n" + userCode.substring(lastIndex + 1) + "\n}";
    }


    /**
     * 得到编译java文件的cmdId
     *
     * @param containerId 容器id
     * @param userId    docker容器中用户代码的文件路径
     * @return cmdId
     */
    private String getCompileCmdId(String containerId, long userId) {
        try {
            return dockerClient.execCreateCmd(containerId)
                    .withCmd(getJavaCompile(userId))
                    .withAttachStdin(true)
                    .withAttachStdout(true)
                    .withAttachStderr(true)
                    .exec().getId();
        } catch (Exception e) {
            throw new RuntimeException("获取编译cmdId失败", e);
        }
    }

    private String getRunCmdId(String containerId, long userId) {
        try {
            return dockerClient.execCreateCmd(containerId)
                    .withCmd(getJavaRun(userId))
                    .withAttachStdin(true)
                    .withAttachStdout(true)
                    .withAttachStderr(true)
                    .exec().getId();
        } catch (Exception e) {
            throw new RuntimeException("获取运行cmdId失败", e);
        }
    }

    public static String[] getJavaCompile(long userId) {
        String[] arr = new String[6];
        String linuxCodeDir = DockerConsts.DOCKER_CODE_DIR + "/java";
        String linuxLibsDir = linuxCodeDir + "/libs";
        String linuxUserCodeDir = linuxCodeDir + "/" + userId;
        arr[0] = "javac";
        arr[1] = "-cp";
        arr[2] = ".:" + linuxLibsDir + "/" + DockerConsts.FASTJSON2_JAR + ":" + linuxLibsDir + "/" + DockerConsts.LOMBOK_JAR;
        arr[3] = linuxUserCodeDir + "/" + DockerConsts.JAVA_CLASS_NAME;
        arr[4] = "-d";
        arr[5] = linuxUserCodeDir;
        return arr;
    }

    public static String[] getJavaRun(long userId) {
        String[] arr = new String[5];
        String linuxCodeDir = DockerConsts.DOCKER_CODE_DIR + "/java";
        String linuxLibsDir = linuxCodeDir + "/libs";
        String linuxUserCodeDir = linuxCodeDir + "/" + userId;
        arr[0] = "java";
        arr[1] = "-cp";
//        fastjson2和lombok的jar包
        arr[2] = linuxUserCodeDir + ":" + linuxLibsDir + "/" + DockerConsts.FASTJSON2_JAR + ":" + linuxLibsDir + "/" + DockerConsts.LOMBOK_JAR;
//        solution类
        arr[3] = DockerConsts.JAVA_FILE_NAME;
//        test_cases.txt
        arr[4] = linuxUserCodeDir + "/" + DockerConsts.TEST_CASE_FILE;
        return arr;
    }
}
